#include <QDebug>
#include <QtPlugin>
#include <QVariant>
#include <QVBoxLayout>
#include <QStackedWidget>
#include <QAction>
#include <QMessageBox>
#include <QFileDialog>
#include <QTimer>
#include <QStringBuilder>
#include "EditorsManager.h"
#include "Include/IEditors.h"
#include "Include/IEditor.h"
#include "Include/Plugins.h"
#include "Include/Project.h"
#include "Include/Settings.h"
#include "HistoryNavigate.h"
#include "FindDialog.h"
#include "OpenedDocsWidget.h"

EditorsManager::EditorsManager(QWidget *parent) :
    QWidget(parent),
    _historyNavi(NULL),
    _find(NULL),
    _oDocs(NULL)
{
    QVBoxLayout *box = new QVBoxLayout(this);
    setLayout(box);

    _watcher = new QFileSystemWatcher(this);
    connect(_watcher, SIGNAL(fileChanged(QString)), SLOT(_onFileChanged(QString)));

    _place = new QStackedWidget(this);
    _find = new FindDialog(&_currentEditor, this);
    layout()->addWidget(_place);
    layout()->addWidget(_find);
    layout()->setContentsMargins(0, 0, 0, 0);

    connect(&_currentEditor, SIGNAL(undoAvailable(bool)), SIGNAL(undoAvailable(bool)));
    connect(&_currentEditor, SIGNAL(redoAvailable(bool)), SIGNAL(redoAvailable(bool)));
    connect(&_currentEditor, SIGNAL(copyAvailable(bool)), SIGNAL(copyAvailable(bool)));
    connect(&_currentEditor, SIGNAL(pasteAvailable(bool)), SIGNAL(pasteAvailable(bool)));
    connect(&_currentEditor, SIGNAL(modifyChanged(bool)), SIGNAL(modifyChanged(bool)));

    connect(_find, SIGNAL(findAvailable(bool)), SIGNAL(findAvailable(bool)));
    connect(_find, SIGNAL(replaceAvailable(bool)), SIGNAL(replaceAvailable(bool)));
    connect(_find, SIGNAL(findNext()), SLOT(findNext()));
    connect(_find, SIGNAL(findPrevious()), SLOT(findPrevious()));
    connect(_find, SIGNAL(replace()), SLOT(replace()));
    connect(_find, SIGNAL(replaceFind()), SLOT(replaceFind()));
    connect(_find, SIGNAL(replaceAll()), SLOT(replaceAll()));

    QAction *a = new QAction(this);
    a->setShortcut(Qt::ControlModifier + Qt::Key_Tab);
    connect(a, SIGNAL(triggered()), SLOT(historyNavigate()));

    _place->addAction(a);
    _find->hide();
}

EditorsManager::~EditorsManager()
{
    for(int i = 0; i < _place->count(); ++i){
        _place->removeWidget(_place->widget(i));
    }
    delete _find;
    foreach(QString key, _editors.keys()){
        delete _editors[key];
    }
    //qDeleteAll(_editors);
    //_editors.clear();
    //qDebug() << "delete EditorsManager";
}

void EditorsManager::openEditor(const QString& file, int offset, bool offsetIsLine)
{
    if (!QFile::exists(file))
        return;

    if (!isEditorOpened(file))
        _openEditor(file);
    else
        activateEditor(file);

    if (offset && !offsetIsLine){
        _currentEditor.jumpOffset(offset);
    }

    if (offset && offsetIsLine){
        _currentEditor.jumpLine(offset);
    }
}

void EditorsManager::closeEditor(const QString& file)
{
    if (isEditorOpened(file)){
        _closeEditor(file);
        _project->closeFile(file);
    }
}

void EditorsManager::closeAllEditors()
{
    foreach(QString name, _stack){
        _closeEditor(name, false);
    }
}

QStringList EditorsManager::openedEditors()
{
    return _stack;
}

bool EditorsManager::isEditorOpened(const QString& file)
{
    return _stack.contains(file);
}

bool EditorsManager::_openEditor(const QString& file)
{
    Plugins::IEditors::EditorType type = Plugins::Settings::instance()->getTypeByExtension(file);
    QString plugName = "libtexteditor.so";
    if (type == Plugins::IEditors::UI)
        plugName = "libdesigner.so";

    PluginWrapper<Plugins::IEditors> eds = loadPlugin<Plugins::IEditors>(plugName);
    eds->setProject(_project);
    _editors[file] = eds->createEditor(type, this);
    QWidget *w = _editors[file]->widget();
    w->setProperty("fileName", file);

    connect(_editors[file], SIGNAL(modifyChanged(bool)), SLOT(onModifyChanged(bool)));
    connect(_editors[file], SIGNAL(closeEditor()), SLOT(closeCurrentEditor()));
    connect(_editors[file], SIGNAL(severityChanged(int)), SLOT(onSeverityChanged(int)));
    connect(_editors[file], SIGNAL(errorListChanged(QAbstractTableModel*)), SLOT(onErrorListChanged(QAbstractTableModel*)));
    connect(_editors[file], SIGNAL(fileNavigate(QString, int)), SIGNAL(fileNavigate(QString, int)));

    _place->addWidget(w);
    _stack.prepend(file);
    emit opened(file);
    _editors[file]->load(file);
    activateEditor(file);
    _watcher->addPath(file);
    return true;
}

bool EditorsManager::_closeEditor(const QString& file, bool activate)
{
    if (_editors.contains(file)){
        if (_editors[file]->isModified()){
            if (QMessageBox::question(this, tr("File is modified"), tr("File is modified, do you want to close this anyway?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
                return false;
        }
        for(int i = 0; i < _place->count(); i++){
            if (_place->widget(i)->property("fileName").toString() == file){
                _place->removeWidget(_place->widget(i));
                _editors.remove(file);
                _stack.removeOne(file);
                _watcher->removePath(file);
                emit closed(file);
                if (activate && _stack.length() > 0)
                    activateEditor(_stack.first());
                break;
            }
        }
    }
    return false;
}

bool EditorsManager::activateEditor(const QString& file)
{
    _currentFile = file;
    for(int i = 0; i < _place->count(); i++){
        if (_place->widget(i)->property("fileName").toString() == _currentFile){
            _place->setCurrentIndex(i);
            _stack.removeOne(file);
            _stack.prepend(file);
            _currentEditor.setEditor(_editors[_currentFile]);
            _place->setFocus();
            _place->currentWidget()->setFocus();
            _currentEditor.setFocus();
            emit activated(file);
            break;
        }
    }
    return false;
}

PluginWrapper<Plugins::IEditor> EditorsManager::currentEditor()
{
    return _currentEditor.editor();
}

void EditorsManager::undo()
{
    _currentEditor.undo();
}

void EditorsManager::redo()
{
    _currentEditor.redo();
}

void EditorsManager::copy()
{
    _currentEditor.copy();
}

void EditorsManager::cut()
{
    _currentEditor.cut();
}

void EditorsManager::paste()
{
    _currentEditor.paste();
}

void EditorsManager::del()
{
    _currentEditor.del();
}

void EditorsManager::selectAll()
{
    _currentEditor.selectAll();
}

void EditorsManager::findNext()
{
    _currentEditor.find(_find->findText(), true);
}

void EditorsManager::findPrevious()
{
    _currentEditor.find(_find->findText(), false);
}

void EditorsManager::replace()
{
    _currentEditor.replace(_find->findText(), _find->replaceText(), false, true);
}

void EditorsManager::replaceFind()
{
    _currentEditor.replace(_find->findText(), _find->replaceText(), true, true);
}

void EditorsManager::replaceFindPrevious()
{
    _currentEditor.replace(_find->findText(), _find->replaceText(), true, false);
}

void EditorsManager::replaceAll()
{
    _currentEditor.replaceAll(_find->findText(), _find->replaceText());
}

void EditorsManager::uppercase()
{
    _currentEditor.uppercase();
}

void EditorsManager::lowercase()
{
    _currentEditor.lowercase();
}

void EditorsManager::capitalize()
{
    _currentEditor.capitalize();
}

void EditorsManager::save()
{
    _watcher->removePath(_currentFile);
    _currentEditor.save();
    _watcher->addPath(_currentFile);
}

void EditorsManager::saveAs()
{

}

void EditorsManager::historyNavigate()
{
    if (_historyNavi) {
        _historyNavi->next();
    } else {
        _historyNavi = new HistoryNavigate(_stack, _place);
        connect(_historyNavi, SIGNAL(closed(QString)), SLOT(delNavigator(QString)));
    }
}

void EditorsManager::delNavigator(const QString& fileName)
{
    activateEditor(fileName);
    _historyNavi->deleteLater();
    _historyNavi = NULL;
}

void EditorsManager::showFind()
{
    if (_find->isVisible())
        _find->activate();
    else
        _find->show();
}

void EditorsManager::resetEditor()
{

    if (!_currentEditor.reset()){
        if (_historyNavi){
            _historyNavi->deleteLater();
            _historyNavi = NULL;
        }
        if (_find->isVisible())
            _find->hide();
    }
    _currentEditor.setFocus();
}

void EditorsManager::setProject(Plugins::Project *prj)
{
    _project = prj;
    if (_oDocs && _project)
        _oDocs->setProjectPath(_project->path());
}

void EditorsManager::onModifyChanged(bool m)
{
    QString fileName = sender()->property("fileName").toString();
    emit modifyChanged(fileName, m);
}

void EditorsManager::closeCurrentEditor()
{
    _project->closeFile(_currentFile);
    closeEditor(_currentFile);
}

QWidget * EditorsManager::openedDocsWidget()
{
    if (!_oDocs){
        _oDocs = new OpenedDocsWidget(this);
        connect(this, SIGNAL(activated(QString)), _oDocs, SLOT(activated(QString)));
        connect(this, SIGNAL(opened(QString)), _oDocs, SLOT(editorOpened(QString)));
        connect(this, SIGNAL(closed(QString)), _oDocs, SLOT(editorClosed(QString)));
        connect(this, SIGNAL(modifyChanged(QString,bool)), _oDocs, SLOT(modifyChanged(QString,bool)));
        connect(_oDocs, SIGNAL(closeEditor(QString)), SLOT(closeEditor(QString)));
        connect(_oDocs, SIGNAL(activateEditor(QString)), SLOT(activateEditor(QString)));
        connect(this, SIGNAL(severityChanged(QString, int)), _oDocs, SLOT(severityChanged(QString, int)));
    }
    return _oDocs;
}

void EditorsManager::onSeverityChanged(int sev)
{
    QString fileName = sender()->property("fileName").toString();
    emit severityChanged(fileName, sev);
}

void EditorsManager::_onFileChanged(const QString & file)
{
    if (_changedFiles.contains(file))
        return;
    _changedFiles.append(file);

    QTimer* timer = new QTimer(this);
    timer->setProperty("file", file);
    timer->setSingleShot(true);
    connect(timer, SIGNAL(timeout()), SLOT(onFileChanged()));
    timer->start(300);
}

void EditorsManager::onFileChanged()//const QString & file
{
    QString file = sender()->property("file").toString();
    if (_changedFiles.contains(file))
        _changedFiles.removeAll(file);
    int btn = QMessageBox::question(this, tr("File was changed"), tr("File %1 was changed externaly, Reload it?").arg(file), QMessageBox::Yes, QMessageBox::No);
    if (btn == QMessageBox::Yes){
        _editors[file]->load(file);
    }
}

void EditorsManager::onErrorListChanged(QAbstractTableModel* model)
{
    QString fileName = sender()->property("fileName").toString();
    if (_currentFile == fileName)
        emit errorListChanged(model);
}

void EditorsManager::jumpCurrentFileLine(int lineNo)
{
    _currentEditor.jumpLine(lineNo);
}

void EditorsManager::jumpToFileLine(const QString& fileName, int line)
{
    openEditor(fileName, line, true);
}

void EditorsManager::openFile()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open file"), Plugins::Settings::instance()->lastUsedWorkspace(), tr("Any files (*)"));
    if (!fileName.isEmpty()){
        Plugins::Settings::instance()->setLastUsedWorkspace(QFileInfo(fileName).absoluteDir().path());
        openEditor(fileName);
    }
}

void EditorsManager::switchSourceUi()
{
    QFileInfo info(_currentFile);
    QString open = info.absolutePath() % QDir::separator() % info.baseName() % (info.suffix() == "py" ? ".ui" : ".py");
    if (QFile::exists(open)){
        openEditor(open);
    }
}

Q_EXPORT_PLUGIN2(editors, EditorsManager)
